虚拟表格来了! 您所在的位置:网站首页 table vue 虚拟表格来了!

虚拟表格来了!

2024-07-13 20:14| 来源: 网络整理| 查看: 265

前言

在 v4 时期,我们为 Table 添加了一个自定义 components 的示例,通过 components.body 替换默认的 ,实现虚拟滚动的效果。但是很多开发者反馈 Demo 中的虚拟表格有很多功能无法实现。例如 固定列、合并行列、展开行 等等。

所以在 v5 中,我们提出了 [RFC] StaticTable for fast perf & virtual scroll support。该 RFC 期望提供一个高性能的 Table.StaticTable,它会默认支持虚拟滚动。但是随着开发进行,我们最终决定 StaticTable 在底层 rc-table 上实现,而在 antd 侧则只需要通过 即可开启。

太长不看

Table 通过 virtual 属性即可开启虚拟滚动能力。同时,原 Table 的功能都能正常使用:

固定列

Fixed Columns

可展开

Expandable

行列组合

Rowspan & Colspan

你可以直接访问 虚拟列表 示例进行体验。

一些细节

antd 的 Table 底层使用了 rc-table 组件,我们的虚拟滚动功能也是复用了上文提到的 components 属性。将中间的 替换为 rc-virtual-list,该组件广泛应用于 antd 的各个虚拟滚动场景中。例如 Select、Tree 都可以见到它的身影。而 rc-virtual-list 本身并不支持横向滚动能力,因而我们在这次改造中,也为其添加了横向滚动的支持。

固定列

在 v4 时期,我们便将 Table 的固定列改造成了 position: sticky 实现。该 CSS 允许你在滚动时,将元素固定在某个位置。从而避免 v3 时期需要额外渲染一份 Table 用于实现固定位置的效果:

Sticky

对于叠加固定,只需要配置不同的偏移量即可:

Stack Sticky

在虚拟滚动中,我们同样可以利用该特性。直接复用 sticky 样式,即可实现固定列的效果。而 rc-virtual-list 需要付出的仅仅是提供横向滚动,而不需要关心固定列的实现。

可展开

在 rc-table 中,我们会将 dataSource 通过 useFlattenRecords 将树状结构打平,从而支持开发者自定义的虚拟滚动能力。感谢 @crawler-django 当年的贡献,因而我们这次并不需要再实现一次打平逻辑。

但是在测试时,我们发现一个奇怪的现象。表格在首次、再次渲染时,会有非常大的卡顿。在进行断点时,它来自于 useFlattenRecords hooks。而测试的代码本身并没有使用可展开树的功能,于是我们对其进行了排查。发现在 useFlattenRecords 中,存在大量的 GC 操作。而这些操作是由于一段不起眼的代码引起的:

// Fake code. Not used in real worldfunction flatten(data: T[] = []) { let tmpList: T[] = []; for (let i = 0; i ({ rowSpan: index % spanCount === 0 ? spanCount : 0, }), }, ]; // WOW! React.useEffect(() => { setSpanCount(5); }, []); return ;};

此外,即便我们统计了 rowSpan 的数据,并且将屏幕外的行也进行渲染仍然不够。它可能会出现 rowSpan 交替的情况:

Multiple Row Span

假设出现最糟的情况,所有的行都和其他的行有 rowSpan,那么我们需要渲染的内容量将会是整个 dataSource。即虚拟滚动不再虚拟。因而,我们需要对其进行裁剪,只渲染可见区域内的 rowSpan,而将屏幕外的无关部分去除:

cut off

所以,反向思考。我们只需要从可见区域出发。然后向上、向下获取被 rowSpan 影响到的 Record。然后仅对提供 rowSpan 的 cell 进行渲染即可:

fully cut off

rc-virtual-list 提供了 extraRender 方法,它会提供当前虚拟滚动中所渲染的行号。我们只需要对这个范围内的 Record 执行一次 onCell 获取每个 cell 的 rowSpan 和 colSpan 信息。既可以知道当前行是否存在对外的 rowSpan 依赖:

// Fake code. Not used in real worldconst extraRender = ({ start, end }) => { // Start record const startRecord = flattenData[start]; columns.forEach((col) => { // `rowSpan` === 0 means upper record has `rowSpan` const { rowSpan } = col.onCell(startRecord, start); }); // End record const endRecord = flattenData[end]; columns.forEach((col) => { // `rowSpan` > 1 means it should extend to next records const { rowSpan } = col.onCell(endRecord, end); });};

需要注意的是,在实际收集过程中,我们会将整个范围有 rowSpan 的 cell 都记录下来而不仅仅是头尾的 Record。然后通过 extraRender 方法将这些 cell 渲染出来(而原本的 Record 对于被 rowSpan 影响到的 cell 则跳过渲染)。这样就可以保证 rowSpan 的正确性了。

当然,这种实现是基于 rowSpan > 1 和 rowSpan = 0 会匹配出现的假设。它不支持 rowSpan 用于挤压至下层的情况,但是对于数据表格而言,这已经足够了。

总结

虚拟滚动是一个非常复杂的功能,它需要考虑的因素非常多。但是我们相信花费这些精力是值得的,开发者不用再在功能和性能之间做取舍,而是可以同时拥有两者。

以上。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有